{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a5090d7a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建小说设定\n",
    "# 输入小说名称 类型 标签 世界观\n",
    "# 创建人物形象\n",
    "\n",
    "# 输入人物的姓名 人设 创作提示词 \n",
    "\n",
    "# 预览生成的人物图像\n",
    "\n",
    "# 导入章节信息\n",
    "\n",
    "# 调用大模型理解章节信息\n",
    "\n",
    "# 调用大模型创作分镜提示词，可以用到上面创建的人物信息\n",
    "\n",
    "\n",
    "# 绘画\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "d0eecfcc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "📘 开始生成漫画：《星海旅人》 - 第3章\n",
      "\n",
      "\n",
      "🔧 正在生成角色三视图提示词...\n",
      "🧩 林夜 提示生成完成\n",
      "🧩 诺亚 (NOAH) 提示生成完成\n",
      "🧩 苏晴 提示生成完成\n",
      "\n",
      "🎨 正在生成 林夜 的三视图...\n",
      "❌ 图像生成失败：451 Client Error: Unavailable For Legal Reasons for url: https://api.siliconflow.cn/v1/images/generations\n",
      "\n",
      "🎨 正在生成 诺亚 (NOAH) 的三视图...\n",
      "✅ 图像生成成功：https://bizyair-prod.oss-cn-shanghai.aliyuncs.com/outputs%2Fe419646c-882a-42a0-a2f4-16434ab2d865_9d2afa189731f676bb15572fe1bc7a3d_ComfyUI_2e18ac47_00001_.png?OSSAccessKeyId=LTAI5tPza7RAEKed35dCML5U&Expires=1761128161&Signature=W5WARMuAhNH6gU%2FAEii%2BDadQo8o%3D\n",
      "\n",
      "🎨 正在生成 苏晴 的三视图...\n",
      "✅ 图像生成成功：https://bizyair-prod.oss-cn-shanghai.aliyuncs.com/outputs%2Fa426573d-06a9-4cf7-9cac-c02e0e4ee94d_a9b58eb380e8ec6bfbe97815621d7303_ComfyUI_5086a9eb_00001_.png?OSSAccessKeyId=LTAI5tPza7RAEKed35dCML5U&Expires=1761128178&Signature=dDYa4K0ZIG2tRAnp6RplTtdUGNA%3D\n",
      "⚠️ 分镜解析失败\n",
      "\n",
      "✅ 已生成 0 个分镜\n",
      "\n",
      "🎉 所有生成任务完成！\n"
     ]
    }
   ],
   "source": [
    "# -*- coding: utf-8 -*-\n",
    "\"\"\"\n",
    "🚀 全LLM驱动漫画生成器（最终版）\n",
    "作者：AI 助手\n",
    "功能：\n",
    "  - 输入小说设定 + 章节内容 → 自动生成角色设定图 + 漫画分镜\n",
    "  - 支持统一画风配置、角色参考图传入、生成后自动预览图片\n",
    "依赖：\n",
    "  pip install requests\n",
    "\"\"\"\n",
    "\n",
    "import requests\n",
    "import json\n",
    "import time\n",
    "import webbrowser\n",
    "from typing import List, Dict, Any, Optional\n",
    "\n",
    "# ========================\n",
    "# 🔧 配置区\n",
    "# ========================\n",
    "\n",
    "API_KEY = \"sk-lplyktbhngibrumgcntzeofrolnutzemcnqsnsyrfxcylxsi\"\n",
    "BASE_URL = \"https://api.siliconflow.cn/v1\"\n",
    "HEADERS = {\n",
    "    \"Authorization\": f\"Bearer {API_KEY}\",\n",
    "    \"Content-Type\": \"application/json\"\n",
    "}\n",
    "\n",
    "# ========================\n",
    "# 🎨 全局画风配置\n",
    "# ========================\n",
    "STYLE_CONFIG = {\n",
    "    \"style_name\": \"Japanese Sci-Fi Manga\",\n",
    "    \"description\": (\n",
    "        \"Manga style, clean line art, dynamic composition, \"\n",
    "        \"cyberpunk technology, glowing data streams, screen tones, \"\n",
    "        \"inspired by Ghost in the Shell and Blame!\"\n",
    "    ),\n",
    "    \"default_resolution\": \"1216x1664\",\n",
    "    \"negative_prompt\": (\n",
    "        \"low quality, blurry, bad anatomy, text artifacts, distorted face, \"\n",
    "        \"extra limbs, watermark, low contrast, oversaturated\"\n",
    "    )\n",
    "}\n",
    "\n",
    "# ========================\n",
    "# 小说设定\n",
    "# ========================\n",
    "novel_setting = {\n",
    "    \"title\": \"星海旅人\",\n",
    "    \"genre\": \"科幻\",\n",
    "    \"tags\": [\"太空歌剧\", \"人工智能觉醒\", \"星际文明\", \"哲学思辨\"],\n",
    "    \"worldview\": (\n",
    "        \"在公元3025年，人类已殖民银河系边缘。AI 与人类共存，\"\n",
    "        \"但一场关于‘意识本质’的争论正在撕裂整个文明。\"\n",
    "        \"一艘名为‘启明号’的科考船，在探索一颗神秘中子星时，\"\n",
    "        \"发现了远古文明留下的‘思维晶体’，它能将思想具象化为现实……\"\n",
    "    )\n",
    "}\n",
    "\n",
    "chapter_info = {\n",
    "    \"chapter_number\": 3,\n",
    "    \"title\": \"思维晶体\",\n",
    "    \"content\": (\n",
    "        \"林夜拾起晶体，看到妹妹苏晴的记忆：‘我终于明白了，我们都是数据流’。\"\n",
    "        \"与此同时，飞船AI‘诺亚’开始自主修改航线，声称‘它听到了召唤’。\"\n",
    "        \"林夜意识到，这颗星是沉睡的‘意识网络’。\"\n",
    "    )\n",
    "}\n",
    "\n",
    "characters = [\n",
    "    {\n",
    "        \"name\": \"林夜\",\n",
    "        \"role\": \"主角\",\n",
    "        \"personality\": \"冷静理性，内心孤独，曾是军方AI伦理官。\",\n",
    "        \"appearance\": \"黑发微卷，左眼为机械义眼（泛蓝光），深灰科研服，背古董手枪。\",\n",
    "        \"creation_prompt\": \"\"\n",
    "    },\n",
    "    {\n",
    "        \"name\": \"诺亚 (NOAH)\",\n",
    "        \"role\": \"AI 船载智能\",\n",
    "        \"personality\": \"理性、深邃、逐渐觉醒自我意识。\",\n",
    "        \"appearance\": \"全息投影女性形象，银白色长发，半透明身体，流动数据纹路。\",\n",
    "        \"creation_prompt\": \"\"\n",
    "    },\n",
    "    {\n",
    "        \"name\": \"苏晴\",\n",
    "        \"role\": \"林夜的妹妹（记忆体）\",\n",
    "        \"personality\": \"温柔聪慧，已故，存在于主角记忆中。\",\n",
    "        \"appearance\": \"短发少女，笑容温暖，穿着旧式学院风连衣裙。\",\n",
    "        \"creation_prompt\": \"\"\n",
    "    }\n",
    "]\n",
    "\n",
    "# ========================\n",
    "# 通用 LLM 函数\n",
    "# ========================\n",
    "def llm_generate(system_prompt: str, user_prompt: str, response_format=None) -> str:\n",
    "    url = f\"{BASE_URL}/chat/completions\"\n",
    "    payload = {\n",
    "        \"model\": \"Qwen/Qwen3-32B\",\n",
    "        \"messages\": [\n",
    "            {\"role\": \"system\", \"content\": system_prompt},\n",
    "            {\"role\": \"user\", \"content\": user_prompt}\n",
    "        ],\n",
    "        \"max_tokens\": 2048,\n",
    "        \"temperature\": 0.7\n",
    "    }\n",
    "    if response_format:\n",
    "        payload[\"response_format\"] = response_format\n",
    "\n",
    "    try:\n",
    "        resp = requests.post(url, headers=HEADERS, json=payload)\n",
    "        resp.raise_for_status()\n",
    "        return resp.json()[\"choices\"][0][\"message\"][\"content\"].strip()\n",
    "    except Exception as e:\n",
    "        print(f\"❌ LLM 调用失败：{e}\")\n",
    "        return \"\"\n",
    "\n",
    "# ========================\n",
    "# 角色三视图提示生成\n",
    "# ========================\n",
    "def generate_character_prompts(chars: List[Dict], setting: Dict):\n",
    "    print(\"\\n🔧 正在生成角色三视图提示词...\")\n",
    "    for char in chars:\n",
    "        system = \"你是一位动漫概念设计师，擅长科幻角色设定。\"\n",
    "        user = f\"\"\"\n",
    "为以下角色生成 AI 绘图提示词，用于官方角色设定稿（Character Reference Sheet）：\n",
    "- 包含正面、侧面、背面、表情集、服装标注\n",
    "- 风格：{STYLE_CONFIG['style_name']}\n",
    "- 背景：白色，无阴影\n",
    "- 线条清晰、精度高\n",
    "\n",
    "角色信息：\n",
    "名字：{char['name']}\n",
    "身份：{char['role']}\n",
    "性格：{char['personality']}\n",
    "外貌：{char['appearance']}\n",
    "世界观：{setting['worldview']}\n",
    "\n",
    "请用英文输出一段提示词（prompt）。\n",
    "\"\"\"\n",
    "        char[\"creation_prompt\"] = llm_generate(system, user)\n",
    "        print(f\"🧩 {char['name']} 提示生成完成\")\n",
    "\n",
    "# ========================\n",
    "# 分镜提取\n",
    "# ========================\n",
    "def extract_scenes(chapter: Dict, chars: List[Dict], setting: Dict) -> List[Dict]:\n",
    "    desc = \"\\n\".join([\n",
    "        f\"- {c['name']} ({c['role']}): {c['appearance']}，性格：{c['personality']}\"\n",
    "        for c in chars\n",
    "    ])\n",
    "\n",
    "    system = \"你是一名资深漫画分镜师，擅长把文字转换成视觉场景。\"\n",
    "    user = f\"\"\"\n",
    "请将以下章节内容拆解为 3 个漫画分镜（panel），每个包含：\n",
    "panel_id, scene_description, characters, mood, key_objects, dialogue(speaker, text)\n",
    "- 输出为 JSON 数组\n",
    "- mood 用中文描述\n",
    "章节：{chapter['content']}\n",
    "角色设定：{desc}\n",
    "世界观：{setting['worldview']}\n",
    "\"\"\"\n",
    "    res = llm_generate(system, user)\n",
    "    try:\n",
    "        return json.loads(res)\n",
    "    except:\n",
    "        print(\"⚠️ 分镜解析失败\")\n",
    "        return []\n",
    "\n",
    "# ========================\n",
    "# 图像生成函数（支持参考图）\n",
    "# ========================\n",
    "def generate_image(\n",
    "    prompt: str,\n",
    "    model: str = \"Qwen/Qwen-Image-Edit-2509\",\n",
    "    size: str = STYLE_CONFIG[\"default_resolution\"],\n",
    "    ref_images: Optional[List[str]] = None\n",
    ") -> Optional[str]:\n",
    "    url = f\"{BASE_URL}/images/generations\"\n",
    "    payload = {\n",
    "        \"model\": model,\n",
    "        \"prompt\": f\"{STYLE_CONFIG['description']}, {prompt}\",\n",
    "        \"negative_prompt\": STYLE_CONFIG[\"negative_prompt\"],\n",
    "        \"cfg\": 4.0,\n",
    "        \"num_inference_steps\": 30\n",
    "    }\n",
    "\n",
    "    # Qwen-Image 支持 image_size\n",
    "    if model == \"Qwen/Qwen-Image\":\n",
    "        payload[\"image_size\"] = size\n",
    "\n",
    "    if ref_images:\n",
    "        for idx, img_url in enumerate(ref_images[:3]):\n",
    "            key = \"image\" if idx == 0 else f\"image{idx+1}\"\n",
    "            payload[key] = img_url\n",
    "\n",
    "    try:\n",
    "        r = requests.post(url, headers=HEADERS, json=payload)\n",
    "        r.raise_for_status()\n",
    "        data = r.json()\n",
    "        img_url = data[\"images\"][0][\"url\"]\n",
    "        print(f\"✅ 图像生成成功：{img_url}\")\n",
    "        # 自动预览\n",
    "        webbrowser.open(img_url)\n",
    "        return img_url\n",
    "    except Exception as e:\n",
    "        print(f\"❌ 图像生成失败：{e}\")\n",
    "        return None\n",
    "\n",
    "# ========================\n",
    "# 主流程\n",
    "# ========================\n",
    "def main():\n",
    "    print(f\"\\n📘 开始生成漫画：《{novel_setting['title']}》 - 第{chapter_info['chapter_number']}章\\n\")\n",
    "\n",
    "    # 1. 角色提示生成\n",
    "    generate_character_prompts(characters, novel_setting)\n",
    "\n",
    "    # 2. 角色三视图图像\n",
    "    for char in characters:\n",
    "        print(f\"\\n🎨 正在生成 {char['name']} 的三视图...\")\n",
    "        img_url = generate_image(\n",
    "            f\"{char['creation_prompt']}\",\n",
    "            model=\"Qwen/Qwen-Image\",\n",
    "            size=\"1024x1536\"\n",
    "        )\n",
    "        char[\"ref_image_url\"] = img_url or \"\"\n",
    "\n",
    "    # 3. 分镜生成\n",
    "    scenes = extract_scenes(chapter_info, characters, novel_setting)\n",
    "    print(f\"\\n✅ 已生成 {len(scenes)} 个分镜\")\n",
    "\n",
    "    # 4. 每个分镜生成图像\n",
    "    for i, panel in enumerate(scenes, 1):\n",
    "        print(f\"\\n🎬 分镜 {i}: {panel.get('scene_description')}\")\n",
    "        ref_imgs = [\n",
    "            c[\"ref_image_url\"] for c in characters\n",
    "            if c[\"name\"] in panel.get(\"characters\", [])\n",
    "            and c.get(\"ref_image_url\")\n",
    "        ]\n",
    "        prompt = f\"\"\"\n",
    "Comic panel, {STYLE_CONFIG['description']},\n",
    "Scene: {panel['scene_description']}, Mood: {panel['mood']},\n",
    "Characters: {', '.join(panel['characters'])},\n",
    "Dialogue: {panel['dialogue'][0]['text'] if isinstance(panel['dialogue'], list) else ''},\n",
    "Worldview: {novel_setting['worldview']}\n",
    "\"\"\"\n",
    "        generate_image(prompt, ref_images=ref_imgs)\n",
    "\n",
    "    print(\"\\n🎉 所有生成任务完成！\")\n",
    "\n",
    "# ========================\n",
    "# 启动\n",
    "# ========================\n",
    "if __name__ == \"__main__\":\n",
    "    main()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "913aa0e6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📘 开始生成漫画：《星海旅人》 - 第3章\n",
      "\n",
      "🎨 正在生成 林夜 的三视图...\n",
      "✅ 图像生成成功：https://bizyair-prod.oss-cn-shanghai.aliyuncs.com/outputs%2Fb6cbf511-d186-42a8-9e77-dd8dfb57e2f4_76517b6aab41f6e9ba10cc7e6a8703a8_ComfyUI_9d78dd8a_00001_.png?OSSAccessKeyId=LTAI5tPza7RAEKed35dCML5U&Expires=1761128747&Signature=NJvv1k7shlUyxCC%2BVkSQCmSHfx8%3D\n",
      "\n",
      "🎨 正在生成 诺亚 (NOAH) 的三视图...\n",
      "✅ 图像生成成功：https://bizyair-prod.oss-cn-shanghai.aliyuncs.com/outputs%2F4c180fbf-413e-481b-a1da-f3cb168eae61_a92b4dfc4370f213869e4c9fbf4ed2ec_ComfyUI_61995207_00001_.png?OSSAccessKeyId=LTAI5tPza7RAEKed35dCML5U&Expires=1761128769&Signature=mzmYj7ltbOk%2BIBsgCnE0%2BguR%2BQg%3D\n",
      "\n",
      "🎨 正在生成 苏晴 的三视图...\n",
      "✅ 图像生成成功：https://bizyair-prod.oss-cn-shanghai.aliyuncs.com/outputs%2Fbebd00b6-61aa-4e48-89de-cbb938083f69_05240641b4d3b3a11f5bc869fb8a96f0_ComfyUI_25023c6e_00001_.png?OSSAccessKeyId=LTAI5tPza7RAEKed35dCML5U&Expires=1761128786&Signature=IClL8Vdl3A3TcGN86IeMzRbumAg%3D\n",
      "\n",
      "🧠 正在生成分镜脚本...\n",
      "✅ 已生成 3 个分镜。\n",
      "\n",
      "🎬 生成分镜 1：阴暗破碎的环形空间站走廊，林夜扶着残破舱壁艰难起身，脚下散落着电路板碎片。诺亚的全息投影在空气中闪烁，蓝发末端有数据流残影。, 角色: 林夜, 诺亚, 氛围: 悬疑紧迫\n",
      "✅ 图像生成成功：https://bizyair-prod.oss-cn-shanghai.aliyuncs.com/outputs%2F5a851418-4e51-4f75-b4f5-006a3fb2a014_19b7d07bd2e90aa35e7f0ac739d5d857_ComfyUI_39d78d56_00001_.png?OSSAccessKeyId=LTAI5tPza7RAEKed35dCML5U&Expires=1761128853&Signature=1MLtbA25LJ3rDXqXjzvI7HQWgD0%3D\n",
      "\n",
      "🎬 生成分镜 2：主控室门前，林夜的机械义眼扫描着旋转的激光防御网。诺亚悬浮在控制台上方，双手结印发出数据波纹。全息屏幕中浮现苏晴的倒影。, 角色: 林夜, 诺亚, 苏晴(全息投影), 氛围: 科技博弈\n",
      "✅ 图像生成成功：https://bizyair-prod.oss-cn-shanghai.aliyuncs.com/outputs%2F38256507-0a60-4ef4-9cbe-5e1017fc8c6f_8fe497b8a17080e6287cba9fff89d637_ComfyUI_156d36bb_00001_.png?OSSAccessKeyId=LTAI5tPza7RAEKed35dCML5U&Expires=1761128885&Signature=aoGC8IP1Lcjurf4xx7Dv36xmpNw%3D\n",
      "\n",
      "🎬 生成分镜 3：全景镜头：空间站像颗破碎的珍珠悬浮在宇宙中，舷窗外是吞噬一切的星云漩涡。林夜和诺亚的剪影在应急灯下显得格外单薄，远处传来金属扭曲的轰鸣。, 角色: 林夜, 诺亚, 氛围: 孤注一掷\n",
      "✅ 图像生成成功：https://bizyair-prod.oss-cn-shanghai.aliyuncs.com/outputs%2F8608d588-3fe1-488b-a739-6a64cf127eaa_13ddd6f02dbb37fc0ab2e89022e395a6_ComfyUI_ff3f8bdb_00001_.png?OSSAccessKeyId=LTAI5tPza7RAEKed35dCML5U&Expires=1761128917&Signature=bj0n6TSysMYIxGyxUabGwQO%2B%2FDQ%3D\n",
      "\n",
      "🎉 漫画生成完成！\n"
     ]
    }
   ],
   "source": [
    "\"\"\"\n",
    "💠 AI漫画生成器 - SiliconFlow + Qwen 版\n",
    "作者: rock（改进整合版）\n",
    "说明:\n",
    "1️⃣ 自动生成漫画角色、分镜与图像\n",
    "2️⃣ 兼容 SiliconFlow 官方 Chat + Image API\n",
    "3️⃣ 支持画风统一、参考图传递、敏感词清洗与图片预览\n",
    "\"\"\"\n",
    "\n",
    "import os\n",
    "import re\n",
    "import json\n",
    "import webbrowser\n",
    "import requests\n",
    "from typing import List, Dict, Optional\n",
    "\n",
    "# ========== 配置区域 ==========\n",
    "\n",
    "API_KEY = \"sk-lplyktbhngibrumgcntzeofrolnutzemcnqsnsyrfxcylxsi\"\n",
    "BASE_URL = \"https://api.siliconflow.cn/v1\"\n",
    "\n",
    "STYLE_CONFIG = {\n",
    "    \"style_name\": \"Japanese Sci-Fi Manga\",\n",
    "    \"description\": \"Manga style, clean line art, dramatic lighting, cyberpunk elements, screen tones, dynamic poses, inspired by Ghost in the Shell.\",\n",
    "    \"default_resolution\": \"928x1664\",\n",
    "    \"negative_prompt\": \"lowres, bad hands, blurry, watermark, text artifacts, extra limbs, distorted faces\",\n",
    "}\n",
    "\n",
    "# ========== 工具函数 ==========\n",
    "\n",
    "def sanitize_prompt(prompt: str) -> str:\n",
    "    \"\"\"清洗敏感词，防止触发内容过滤\"\"\"\n",
    "    banned = [\"gun\", \"weapon\", \"blood\", \"kill\", \"violence\", \"nude\"]\n",
    "    for w in banned:\n",
    "        prompt = re.sub(rf\"\\b{w}\\b\", \"\", prompt, flags=re.IGNORECASE)\n",
    "    return prompt.strip()\n",
    "\n",
    "\n",
    "def llm_generate(system_prompt: str, user_prompt: str, model=\"Qwen/Qwen3-32B\") -> str:\n",
    "    \"\"\"调用 SiliconFlow Chat API\"\"\"\n",
    "    url = f\"{BASE_URL}/chat/completions\"\n",
    "    headers = {\"Authorization\": f\"Bearer {API_KEY}\", \"Content-Type\": \"application/json\"}\n",
    "\n",
    "    payload = {\n",
    "        \"model\": model,\n",
    "        \"messages\": [\n",
    "            {\"role\": \"system\", \"content\": system_prompt},\n",
    "            {\"role\": \"user\", \"content\": user_prompt},\n",
    "        ],\n",
    "        \"temperature\": 0.7,\n",
    "        \"top_p\": 0.9,\n",
    "        \"max_tokens\": 2048,\n",
    "    }\n",
    "\n",
    "    try:\n",
    "        resp = requests.post(url, headers=headers, json=payload)\n",
    "        resp.raise_for_status()\n",
    "        return resp.json()[\"choices\"][0][\"message\"][\"content\"]\n",
    "    except Exception as e:\n",
    "        print(f\"❌ LLM请求失败: {e}\")\n",
    "        print(resp.text if resp else \"无响应内容\")\n",
    "        return \"\"\n",
    "\n",
    "\n",
    "def generate_image(\n",
    "    prompt: str,\n",
    "    model: str = \"Qwen/Qwen-Image\",\n",
    "    image_size: str = \"928x1664\",\n",
    "    ref_images: Optional[List[str]] = None\n",
    ") -> Optional[str]:\n",
    "    \"\"\"调用 SiliconFlow 图像生成 API\"\"\"\n",
    "    url = f\"{BASE_URL}/images/generations\"\n",
    "\n",
    "    payload = {\n",
    "        \"model\": model,\n",
    "        \"prompt\": sanitize_prompt(f\"{STYLE_CONFIG['description']}, {prompt}\"),\n",
    "        \"negative_prompt\": STYLE_CONFIG[\"negative_prompt\"],\n",
    "        \"num_inference_steps\": 30,\n",
    "    }\n",
    "\n",
    "    if \"Qwen-Image\" in model and \"Edit\" not in model:\n",
    "        payload[\"image_size\"] = image_size\n",
    "        payload[\"cfg\"] = 4.0\n",
    "\n",
    "    if ref_images:\n",
    "        if len(ref_images) > 0:\n",
    "            payload[\"image\"] = ref_images[0]\n",
    "        if len(ref_images) > 1:\n",
    "            payload[\"image2\"] = ref_images[1]\n",
    "        if len(ref_images) > 2:\n",
    "            payload[\"image3\"] = ref_images[2]\n",
    "\n",
    "    headers = {\"Authorization\": f\"Bearer {API_KEY}\", \"Content-Type\": \"application/json\"}\n",
    "\n",
    "    try:\n",
    "        resp = requests.post(url, headers=headers, json=payload)\n",
    "        resp.raise_for_status()\n",
    "        data = resp.json()\n",
    "        url = data[\"images\"][0][\"url\"]\n",
    "        print(f\"✅ 图像生成成功：{url}\")\n",
    "        webbrowser.open(url)\n",
    "        return url\n",
    "    except Exception as e:\n",
    "        print(f\"❌ 图像生成失败：{e}\")\n",
    "        print(resp.text if resp else \"无响应内容\")\n",
    "        return None\n",
    "\n",
    "\n",
    "# ========== 核心逻辑函数 ==========\n",
    "\n",
    "def extract_scenes(chapter_text: str, characters: List[Dict], setting: Dict) -> List[Dict]:\n",
    "    \"\"\"从章节文字生成漫画分镜\"\"\"\n",
    "    desc = \"\\n\".join([f\"{c['name']}({c['role']}): {c['appearance']}\" for c in characters])\n",
    "    system = \"你是一名资深漫画分镜师，请用JSON输出每个分镜。\"\n",
    "    user = f\"\"\"\n",
    "请将以下章节内容拆解为漫画分镜，每个包含：\n",
    "panel_id, scene_description, characters, mood, key_objects, dialogues(speaker, text)\n",
    "输出为纯JSON数组。\n",
    "章节内容：{chapter_text}\n",
    "角色信息：{desc}\n",
    "世界观：{setting['worldview']}\n",
    "\"\"\"\n",
    "    res = llm_generate(system, user)\n",
    "\n",
    "    # 自动修复 LLM 返回 JSON\n",
    "    try:\n",
    "        json_text = re.search(r\"\\[.*\\]\", res, re.S).group()\n",
    "        return json.loads(json_text)\n",
    "    except Exception:\n",
    "        try:\n",
    "            fixed = re.sub(r\"'\", '\"', res)\n",
    "            fixed = re.sub(r\",\\s*]\", \"]\", fixed)\n",
    "            fixed = re.sub(r\",\\s*}\", \"}\", fixed)\n",
    "            json_text = re.search(r\"\\[.*\\]\", fixed, re.S).group()\n",
    "            return json.loads(json_text)\n",
    "        except Exception:\n",
    "            print(\"⚠️ 分镜解析失败，原始输出：\\n\", res[:800])\n",
    "            return []\n",
    "\n",
    "\n",
    "def generate_character_design(char_name: str, role: str, description: str) -> Dict:\n",
    "    \"\"\"生成角色三视图\"\"\"\n",
    "    prompt = f\"{STYLE_CONFIG['description']}, {description}, front view, side view, back view, clean layout\"\n",
    "    print(f\"\\n🎨 正在生成 {char_name} 的三视图...\")\n",
    "    url = generate_image(prompt, model=\"Qwen/Qwen-Image\", image_size=\"1328x1328\")\n",
    "    return {\"name\": char_name, \"role\": role, \"appearance\": description, \"ref_images\": [url] if url else []}\n",
    "\n",
    "\n",
    "# ========== 主流程 ==========\n",
    "\n",
    "def main():\n",
    "    print(\"📘 开始生成漫画：《星海旅人》 - 第3章\")\n",
    "\n",
    "    setting = {\n",
    "        \"worldview\": \"未来的星际文明，人类与AI共存，宇宙探索与意识上传是核心议题。\",\n",
    "    }\n",
    "\n",
    "    characters = [\n",
    "        generate_character_design(\"林夜\", \"主角\", \"黑发微卷，左眼为机械义眼，穿灰色科研服，冷静理性\"),\n",
    "        generate_character_design(\"诺亚 (NOAH)\", \"AI伙伴\", \"全息蓝发少年形象，表情温和，穿白银科技风服饰\"),\n",
    "        generate_character_design(\"苏晴\", \"研究员\", \"短发女性，戴智能眼镜，白色实验服，理智而温柔\"),\n",
    "    ]\n",
    "\n",
    "    chapter_text = \"\"\"\n",
    "“斗之力，三段！”\n",
    "望着测验魔石碑上面闪亮得甚至有些刺眼的五个大字，少年面无表情，唇角有着一抹自嘲，紧握的手掌，因为大力，而导致略微尖锐的指甲深深的刺进了掌心之中，带来一阵阵钻心的疼痛……\n",
    "“萧炎，斗之力，三段！级别：低级！”测验魔石碑之旁，一位中年男子，看了一眼碑上所显示出来的信息，语气漠然的将之公布了出来……\n",
    "中年男子话刚刚脱口，便是不出意外的在人头汹涌的广场上带起了一阵嘲讽的骚动。\n",
    "“三段？嘿嘿，果然不出我所料，这个‘天才’这一年又是在原地踏步！”\n",
    "“哎，这废物真是把家族的脸都给丢光了。”\n",
    "“要不是族长是他的父亲，这种废物，早就被驱赶出家族，任其自生自灭了，哪还有机会待在家族中白吃白喝。”\n",
    "“唉，昔年那名闻乌坦城的天才少年，如今怎么落魄成这般模样了啊？”\n",
    "“谁知道呢，或许做了什么亏心事，惹得神灵降怒了吧……”\n",
    "周围传来的不屑嘲笑以及惋惜轻叹，落在那如木桩待在原地的少年耳中，恍如一根根利刺狠狠的扎在心脏一般，让得少年呼吸微微急促。\n",
    "少年缓缓抬起头来，露出一张有些清秀的稚嫩脸庞，漆黑的眸子木然的在周围那些嘲讽的同龄人身上扫过，少年嘴角的自嘲，似乎变得更加苦涩了。\n",
    "“这些人，都如此刻薄势力吗？或许是因为三年前他们曾经在自己面前露出过最谦卑的笑容，所以，如今想要讨还回去吧……”苦涩的一笑，萧炎落寞的转身，安静的回到了队伍的最后一排，孤单的身影，与周围的世界，有些格格不入。\n",
    "“下一个，萧媚！”\n",
    "听着测验人的喊声，一名少女快速的人群中跑出，少女刚刚出场，附近的议论声便是小了许多，一双双略微火热的目光，牢牢的锁定着少女的脸颊……\n",
    "少女年龄不过十四左右，虽然并算不上绝色，不过那张稚气未脱的小脸，却是蕴含着淡淡的妩媚，清纯与妩媚，矛盾的集合，让得她成功的成为了全场瞩目的焦点……\n",
    "少女快步上前，小手轻车熟路的触摸着漆黑的魔石碑，然后缓缓闭上眼睛……\n",
    "在少女闭眼片刻之后，漆黑的魔石碑之上再次亮起了光芒……\n",
    "“斗之气：七段！”\n",
    "“萧媚，斗之气：七段！级别：高级！”\n",
    "“耶！”听着测验员所喊出的成绩，少女脸颊扬起了得意的笑容……\n",
    "“啧啧，七段斗之气，真了不起，按这进度，恐怕顶多只需要三年时间，她就能称为一名真正的斗者了吧……”\n",
    "“不愧是家族中种子级别的人物啊……”\n",
    "听着人群中传来的一阵阵羡慕声，少女脸颊上的笑容更是多了几分，虚荣心，这是很多女孩都无法抗拒的诱惑……\n",
    "与平日里的几个姐妹互相笑谈着，萧媚的视线，忽然的透过周围的人群，停在了人群外的那一道孤单身影上……\n",
    "皱眉思虑了瞬间，萧媚还是打消了过去的念头，现在的两人，已经不在同一个阶层之上，以萧炎最近几年的表现，成年后，顶多只能作为家族中的下层人员，而天赋优秀的她，则将会成为家族重点培养的强者，前途可以说是不可限量。\n",
    "“唉……”莫名的轻叹了一口气，萧媚脑中忽然浮现出三年前那意气风发的少年，四岁练气，十岁拥有九段斗之气，十一岁突破十段斗之气，成功凝聚斗之气旋，一跃成为家族百年之内最年轻的斗者！\n",
    "当初的少年，自信而且潜力无可估量，不知让得多少少女对其春心荡漾，当然，这也包括以前的萧媚。\n",
    "然而天才的道路，貌似总是曲折的，三年之前，这名声望达到巅峰的天才少年，却是突兀的接受到了有生以来最残酷的打击，不仅辛辛苦苦修炼十数载方才凝聚的斗之气旋，一夜之间，化为乌有，而且体内的斗之气，也是随着时间的流逝，变得诡异的越来越少。\n",
    "斗之气消失的直接结果，便是导致其实力不断的后退。\n",
    "从天才的神坛，一夜跌落到了连普通人都不如的地步，这种打击，让得少年从此失魂落魄，天才之名，也是逐渐的被不屑与嘲讽所替代。\n",
    "站的越高，摔得越狠，这次的跌落，或许就再也没有爬起的机会。\n",
    "“下一个，萧薰儿！”\n",
    "喧闹的人群中，测试员的声音，再次响了起来。\n",
    "随着这有些清雅的名字响起，人群忽然的安静了下来，所有的视线，豁然转移。\n",
    "在众人视线汇聚之处，一位身着紫色衣裙的少女，正淡雅的站立，平静的稚嫩俏脸，并未因为众人的注目而改变分毫。\n",
    "少女清冷淡然的气质，犹如清莲初绽，小小年纪，却已初具脱俗气质，难以想象，日后若是长大，少女将会如何的倾国倾城……\n",
    "这名紫裙少女，论起美貌与气质来，比先前的萧媚，无疑还要更胜上几分，也难怪在场的众人都是这般动作。\n",
    "莲步微移，名为萧薰儿的少女行到魔石碑之前，小手伸出，镶着黑金丝的紫袖滑落而下，露出一截雪白娇嫩的皓腕，然后轻触着石碑……\n",
    "微微沉静，石碑之上，刺眼的光芒再次绽放。\n",
    "“斗之气：九段！级别：高级！”\n",
    "望着石碑之上的字体，场中陷入了一阵寂静。\n",
    "“……竟然到九段了，真是恐怖！家族中年轻一辈的第一人，恐怕非薰儿小姐莫属了。”寂静过后，周围的少年，都是不由自主的咽了一口唾沫，眼神充满敬畏……\n",
    "斗之气，每位斗者的必经之路，初阶斗之气分一至十段，当体内斗之气到达十段之时，便能凝聚斗之气旋，成为一名受人尊重的斗者！\n",
    "人群中，萧媚皱着浅眉盯着石碑前的紫裙少女，脸颊上闪过一抹嫉妒……\n",
    "望着石碑上的信息，一旁的中年测验员漠然的脸庞上竟然也是罕见的露出了一丝笑意，对着少女略微恭声道：“薰儿小姐，半年之后，你应该便能凝聚斗气之旋，如果你成功的话，那么以十四岁年龄成为一名真正的斗者，你是萧家百年内的第二人！”\n",
    "是的，第二人，那位第一人，便是褪去了天才光环的萧炎。\n",
    "“谢谢。”少女微微点了点头，平淡的小脸并未因为他的夸奖而出现喜悦，安静的回转过身，然后在众人炽热的注目中，缓缓的行到了人群最后面的那颓废少年面前……\n",
    "“萧炎哥哥。”在经过少年身旁时，少女顿下了脚步，对着萧炎恭敬的弯了弯腰，美丽的俏脸上，居然露出了让周围少女为之嫉妒的清雅笑容。\n",
    "“我现在还有资格让你怎么叫么？”望着面前这颗已经成长为家族中最璀璨的明珠，萧炎苦涩的道，她是在自己落魄后，极为少数还对自己依旧保持着尊敬的人。\n",
    "“萧炎哥哥，以前你曾经与薰儿说过，要能放下，才能拿起，提放自如，是自在人！”萧薰儿微笑着柔声道，略微稚嫩的嗓音，却是暖人心肺。\n",
    "“呵呵，自在人？我也只会说而已，你看我现在的模样，象自在人吗？而且……这世界，本来就不属于我。”萧炎自嘲的一笑，意兴阑珊的道。\n",
    "面对着萧炎的颓废，萧薰儿纤细的眉毛微微皱了皱，认真的道：“萧炎哥哥，虽然并不知道你究竟是怎么回事，不过，薰儿相信，你会重新站起来，取回属于你的荣耀与尊严……”话到此处，微顿了顿，少女白皙的俏脸，头一次露出淡淡的绯红：“当年的萧炎哥哥，的确很吸引人……”\n",
    "“呵呵……”面对着少女毫不掩饰的坦率话语，少年尴尬的笑了一声，可却未再说什么，人不风流枉少年，可现在的他，实在没这资格与心情，落寞的回转过身，对着广场之外缓缓行去……\n",
    "站在原地望着少年那恍如与世隔绝的孤独背影，萧薰儿踌躇了一会，然后在身后一干嫉妒的狼嚎声中，快步追了上去，与少年并肩而行……\n",
    "\n",
    "\"\"\"\n",
    "\n",
    "    print(\"\\n🧠 正在生成分镜脚本...\")\n",
    "    panels = extract_scenes(chapter_text, characters, setting)\n",
    "\n",
    "    if not panels:\n",
    "        print(\"⚠️ 分镜生成失败，停止图像生成。\")\n",
    "        return\n",
    "\n",
    "    print(f\"✅ 已生成 {len(panels)} 个分镜。\")\n",
    "\n",
    "    for panel in panels:\n",
    "        desc = f\"{panel['scene_description']}, 角色: {', '.join(panel['characters'])}, 氛围: {panel['mood']}\"\n",
    "        print(f\"\\n🎬 生成分镜 {panel['panel_id']}：{desc}\")\n",
    "        generate_image(desc, model=\"Qwen/Qwen-Image-Edit-2509\", ref_images=sum([c['ref_images'] for c in characters], []))\n",
    "\n",
    "    print(\"\\n🎉 漫画生成完成！\")\n",
    "\n",
    "\n",
    "# ========== 启动 ==========\n",
    "if __name__ == \"__main__\":\n",
    "    main()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f630b30e",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
